Alien Worlds API Core
Source
Alien Worlds API Core is a comprehensive TypeScript/Node.js package designed to provide a solid infrastructure for building APIs and history tools for the Alien Worlds project. The package is organized into several modules that include API building, a clean architectural structure, blockchain interactions, configuration management, and utility functions. Additionally, it employs Inversify for inversion of control (IoC) to facilitate efficient dependency management.
Table of Contents
- Installation
- IoC (Inversion of Control)
- API
- Architecture
- Blockchain
- Config
- Utils
- Contributing
- License
Installation
yarn add @alien-worlds/aw-core
IoC (Inversion of Control)
Inversion of Control (IoC) is implemented using InversifyJS, a powerful and lightweight inversion of control container for JavaScript and Node.js apps powered by TypeScript. IoC promotes code modularity, making the system more flexible, maintainable, and scalable.
Helpful links:
API
This module comprises the core components necessary for creating APIs. It consists of enumerations, error types, route class, and other types that allow efficient API design.
setupRouteHandler(route)
This function sets up the route handler function for a given route. It takes a route
object as input and returns a promise that resolves to the route handler function. The route handler function is responsible for handling incoming requests and generating appropriate responses based on the provided route configuration.
The setupRouteHandler
function supports any web framework and can be used in conjunction with the Route
class to define routes. It provides flexibility and extensibility, allowing developers to customize the request handling logic by specifying hooks, validators, and authorization functions within the route configuration.
Route
Class
The Route
class is a generic class that represents a route in your web application. It provides a flexible and modular approach to define routes and handle incoming requests. Developers can extend the Route
class and use derived classes such as GetRoute
, PostRoute
, PatchRoute
, PutRoute
, and DeleteRoute
to define routes for different HTTP methods.
The Route
class allows you to specify the HTTP method, path, handler function and configuration options for each route. It supports the use of hooks, validators, authorization an io
functions to customize the request handling process. RouteIO
is used to transform data from requests to unified input for the controller and from controller output to the response.
The class is designed to be compatible with any web framework you choose to use, making it a versatile choice for building APIs or handling server-side routes.
Using the Route
class and its derived classes, developers can create a clean and structured API for their web application, improving code organization and maintainability.
// An example of the RouteIO class implementation
export class ListPlanetsRouteIO implements RouteIO {
public toResponse(output: ListPlanetsOutput): Response {
const { result, ... } = output;
if (result.isFailure) {
const {
failure: { error },
} = result;
if (error) {
return {
status: 500,
body: [],
};
}
}
return {
status: 200,
body: result.content.map(planet => planet.toJSON()),
};
}
public fromRequest(request: Request): ListPlanetsInput {
const { params: { name } , ... } = request;
return ListPlanetsInput.create(name);
}
}
// An example of using the GetRoute class
// Note: 'io' is optional, if you don't specify an instance, default will be used.
export class ListPlanetsRoute extends GetRoute {
public static create(handler: RouteHandler, config: PlanetsApiConfig) {
return new ListPlanetsRoute(handler, config);
}
private constructor(handler: RouteHandler, config: PlanetsApiConfig, io: IO) {
super(`/${config.version}/planets/list`, handler, {
io: new ListPlanetsRouteIO(),
/* ... OR ...
io: {
fromRequest: (request: Request) => ({
toJSON(): () => ({ ... })
}),
toResponse: (output: ListPlanetsOutput) => ({
status: ...,
body: ...
})
},
*/
hooks: {
pre: (request: Request) => {
// Any operations not related to logic but only to the presentation layer
// and which should be performed before creating input for the controller.
},
post: (output: ListPlanetsOutput) => {
// Any operations not related to logic but only to the presentation layer
// and which should be performed before sending the response.
},
},
});
}
}
Helpful links:
Architecture
The architecture module, divided into data and domain layers, follows clean architecture principles. Adopting this approach enhances the code's flexibility, maintainability, and testability. It allows for independent and reusable code components, ensuring that code style remains consistent across different contributors.
The clean architecture paradigm also promotes separation of concerns by dividing the code into layers. The use of this design pattern facilitates the ability to change one aspect of the system without affecting others. This is due to the decoupling of the software into independent layers, thereby reducing the complexity of the codebase, increasing readability, and improving overall code quality.
Dependency Injector
As DependencyInjector
is an abstract class, it cannot be instantiated directly. Its purpose is to define a template for dependency injectors, making the dependency management more structured and maintainable.
Data Layer
The data layer contains base classes and types for data layer components such as:
- DataSource: Represents a general interface for the data sources in the application.
find(query?: Query)
count(query?: Query)
aggregate(query: Query)
update(query: Query)
insert(query: Query)
remove(query: Query)
startTransaction(options?: UnknownObject)
commitTransaction()
rollbackTransaction()
- Mapper: The interface of a class whose instance changes the entity to the model of a specific database and vice versa.
toEntity(model: ModelType)
fromEntity(entity: EntityType)
getEntityKeyMapping(key: string)
- MapperImpl: Basic mapper implementation that can be inherited and extended. It contains a basic fromEntity mechanism for extracting mapping functions assigned to entity parameters.
- QueryBuilders: Collection of query builders for different types of operations (find, count, update, remove, and aggregate).
buildFindQuery(params: FindParams)
buildCountQuery(params: CountParams)
buildUpdateQuery(updates: UpdateType[], where: Where[], methods: UpdateMethod[])
buildRemoveQuery(params: RemoveParams)
buildAggregationQuery(params: AggregationParams)
- RepositoryImpl: A generic repository for managing database interactions.
Domain Layer
The domain layer consists of basic components and types such as:
- Entity: Core representation of an object in the system.
static create(...args: unknown[])
static getDefault()
toJSON()
- QueryBuilder and QueryParams: Abstract QueryBuilder class and parameters for various queries (FindParams, CountParams, AggregationParams, RemoveParams, UpdateParams).
- Failure: Represents a failure as a result of an error in executing a use case or repository operation.
static fromError<T = Error>(error: T, throwable = false, reportable = false)
static withMessage(message: string, throwable = false, reportable = false)
- ReadOnlyRepository and Repository: Abstract classes defining read-only and mutable repository methods.
count(paramsOrBuilder?: CountParams | QueryBuilder)
find(paramsOrBuilder?: FindParams | QueryBuilder)
update(paramsOrBuilder: UpdateParams | QueryBuilder)
add(entities: EntityType[])
remove(paramsOrBuilder: RemoveParams | QueryBuilder)
- Result: Represents the result of executing a use case or repository operation. It can return either a Failure object or the typed content.
static withContent<ContentType>(content: ContentType)
static withoutContent()
static withFailure<ErrorType>(failure: Failure<ErrorType>)
- UseCase: Abstract UseCase class for encapsulating business logic.
execute(...rest: unknown[])
- Where: A class used to build 'Where' clauses. Used to build database queries within query builders.
Helpful links:
Blockchain
The blockchain component is divided into data and domain layers. It contains the necessary types and components to interact with the blockchain.
Data Layer
- RpcSource: Abstraction for the RPC connection. It contains methods to retrieve table rows and contract stats.
getTableRows<RowType = unknown>(options: GetTableRowsOptions)
getContractStats(account: string)
getInfo()
getHeadBlockNumber()
getLastIrreversibleBlockNumber()
Domain Layer
- Entities like ContractAction, ContractDelta, ContractEncodedAbi and ContractUnknownData to represent different blockchain transaction aspects.
- SmartContractService: interface with one method getStats. The concrete service should implement methods to retrieve desired table rows of the contract.
- BlockchainService: An abstraction of the service that is used to download blockchain data and statistics.
getInfo()
getHeadBlockNumber()
getLastIrreversibleBlockNumber()
- AbiService: An abstract class that represents the ABI service. The purpose of the service is to download ABI(s) data from the web.
fetchAbis(contract: string)
- Serializer: An abstraction of tools for serializing and deserializing blockchain content. The implementation depends on the type of blockchain.
getAbiFromHex(hex: string)
getHexFromAbi(abi: AbiType)
getTypesFromAbi(abi: UnknownObject)
serialize(value: unknown, type?: string, types?: Map<string, unknown>, ...args: unknown[])
deserialize(value: unknown, type?: string, types?: Map<string, unknown>, ...args: unknown[])
deserializeActionData(contract: string, action: string, data: Uint8Array, abi: string | UnknownObject, ...args: unknown[])
deserializeTableRow(row: Uint8Array, abi?: string | UnknownObject, ...args: unknown[])
deserializeTableRowData(table: string, data: Uint8Array, abi: string | UnknownObject, ...args: unknown[])
deserializeTransaction(contract: string, data: Uint8Array, abi?: string | UnknownObject, ...args: unknown[])
deserializeBlock(data: DataType, abi?: string | UnknownObject, ...args: unknown[])
hexToUint8Array(value: string)
uint8ArrayToHex(value: Uint8Array)
- Types related to the smart contract components.